package estadistec.logica.parser;

import estadistec.excepciones.Excepcion;

/**
 * Esta clase lee la información de todos los operadores de un archivo:
 *  número de identificación de la función correspondiente, cantidad
 *  y tipos de los parámetros, tipo resultado.
 *
 * Utiliza los tipos enumerados en DataObjectTipos.java
 *
 * No modificar esta clase (jaja), solo modificar el archivo de entrada
 *  si se quiere meter otra función al diccionario de funciones.
 */
import java.io.IOException;
import java.io.File;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Scanner;

/**
 * Esta es la clase que carga el diccionario de datos.
 * Por eficiencia solo debe ser cargada una vez, así que sigue
 *  el patrón de diseño Singleton
 */
public class DiccionarioTipos implements Constantes
 {
	 /* formato de este archivo:
	  * operador id cantParametros retorno [tipo1 tipo2 ...]
	  * Ej de la suma:
	  * + 104 2 ENTERO ENTERO ENTERO
	  * * 105 2 DOUBLE ENTERO ENTERO
	  * + 106 2 STRING ENTERO STRING
	  * sen 107 1 FLOTANTE ENTERO
	  *
	  * Puede tener cualquier cantidad de espacios en blanco, cambios
	  *  de línea, e incluso tener la especificación de una sola func
	  *  en varias líneas.
	  */


	/**
	 * Variable static que guarda la única instancia de esta clase disponible,
	 *  siguiendo el patrón de diseño Singletone.
	 */
	static private DiccionarioTipos instancia = null;

	/**
	 * Tabla que contiene las listas de funciones dado un operador
	 * Es para relacionar 1-N: una llave (operador) a N funciones
	 */
	protected Hashtable<String, ArrayList<DiccionarioTiposInfo>> tablaPorOperador;
	/**
	 * Tabla de relación 1-1, la llave es el identificador de la función.
	 * Contiene el mismo contenido que tablaPorOperador pero ordenado por
	 *  número de identificador o llave única.
	 */
	protected Hashtable<Integer,DiccionarioTiposInfo> tablaPorIdentificador;

	/**
	 * No permite ser instanciado (new) directamente, así solo necesita una
	 *  sola instancia durante todo el transcurso de la ejecución del programa.
	 *  Dicha instancia es dada a "todos" mediante el getInstance().
	 */
	public static DiccionarioTipos getInstance()
	{
		if(instancia == null)
		{
			instancia = new DiccionarioTipos ();
		}
		return instancia;
	}

	/**
	 * Constructor privado de esta clase.
	 */
	private DiccionarioTipos()
	{
		tablaPorOperador = new Hashtable<String, ArrayList<DiccionarioTiposInfo>>();
		tablaPorIdentificador = new Hashtable<Integer,DiccionarioTiposInfo>();
		init();
	}

	/**
	 * Inicializa este diccionario.
	 * Es solo llamado una vez durante toda la ejecución.
	 * Se encarga de leer el archivo de diccionario de datos, cargarlo
	 *  en memoria y hacer la estrucctura del diccionario en memoria.
	 */
	private void init()
	{
		Scanner sca;
		try{
			sca = new Scanner(new File(NOMBRE_ARCHIVO_DICCIONARIO_DATOS));

			while(sca.hasNext())
			{
				//leo el nombre del operador
				String op = sca.next();
				//saco la tabla respectiva (o la creo)
				ArrayList<DiccionarioTiposInfo> info;
				if(this.tablaPorOperador.containsKey(op))
				{
					info = this.tablaPorOperador.get(op);
				}
				else
				{
					info = new ArrayList<DiccionarioTiposInfo>();
					this.tablaPorOperador.put(op, info);
				}

				//saco el id de la funcion, cantidad de parámetros y tipo retorno
				Integer id = new Integer(sca.nextInt());
				int cantParam = sca.nextInt();
				String retorno = sca.next();

				DiccionarioTiposInfo infoOp = new DiccionarioTiposInfo(id, cantParam, retorno);


				for(int i = 0; i<cantParam; i++)
				{
					String param = sca.next();
					infoOp.agregarParametro(param);
				}

				info.add(infoOp);
				this.tablaPorIdentificador.put(id, infoOp);
			}
			sca.close();
		}
		catch (IOException io)
		{
			io.printStackTrace();
			System.out.println(REVISAR_ARCHIVO+NOMBRE_ARCHIVO_DICCIONARIO_DATOS);
		}
	}

	/**
	 * Devuelve la información de una función dado su identificador.
	 * Este método devuelve la información relacionada a la función
	 *  del identificador devuelto por los "buscarFuncion" con
	 *  parámetros String.
	 * @param identificador Llave única de la función.
	 * @return Si la función no existe devuelve null.
	 */
	public DiccionarioTiposInfo buscarFuncion(int identificador)
	{
		return this.tablaPorIdentificador.get(new Integer(identificador));
	}

	/**
	 * Devuelve el identificador de la función dados este operador.
	 */
	public int buscarFuncion(String op) throws Excepcion
	{
		ArrayList<DiccionarioTiposInfo> listaCumplenCantParam =
				buscaFuncionAux(op,0);
		if(listaCumplenCantParam.size()>0)
		{
			DiccionarioTiposInfo info = listaCumplenCantParam.get(0);
			return info.getIdFuncion();
		}
		return FUNC_DESC;
	}


	/**
	 * Devuelve el identificador de la función dados este parámetro
	 *  y este operador.
	 */
	public int buscarFuncion(String op, DataObjectTipos tipoA) throws Excepcion
	{
		ArrayList<DiccionarioTiposInfo> listaCumplenCantParam =
				buscaFuncionAux(op,1);
		for(DiccionarioTiposInfo dti : listaCumplenCantParam)
		{
			if(dti.getCantParametroTipo(tipoA) > 0)
			{
				return dti.getIdFuncion();
			}
		}
		return FUNC_DESC;

	}

	/**
	 * Devuelve el identificador de la función dados estos parámetros
	 *  y este operador.
	 */
	public int buscarFuncion(String op, DataObjectTipos tipoA,
		DataObjectTipos tipoB) throws Excepcion
	{
		ArrayList<DiccionarioTiposInfo> listaCumplenCantParam =
				buscaFuncionAux(op,2);

		//este if es para mejorar el rendimiento
		//hacer el if por fuera en lugar de dos internos.
		if(tipoA == tipoB)
		{
			for(DiccionarioTiposInfo dti : listaCumplenCantParam)
			{
				//tienen que ser dos por ser árbol binario
				if(dti.getCantParametroTipo(tipoA) == 2)
				{
					return dti.getIdFuncion();
				}
			}
		}
		else
		{
			for(DiccionarioTiposInfo dti : listaCumplenCantParam)
			{
				//tienen al menos un parámetro de ese tipo
				if(dti.getCantParametroTipo(tipoA) > 0 &&
				   dti.getCantParametroTipo(tipoB) > 0)
				{
					return dti.getIdFuncion();
				}
			}
		}

		return FUNC_DESC;
	}


	/**
	 * Busca la lista de funciones con ese operador y esa
	 *  cantidad de parámentros
	 */
	protected ArrayList<DiccionarioTiposInfo> buscaFuncionAux(String op, int cantParam)
			throws Excepcion
	{
		//recupera la lista de funciones identificados bajo ese operador
		ArrayList<DiccionarioTiposInfo> listaFunciones = tablaPorOperador.get(op);
		if(listaFunciones == null)
		{
			throw new Excepcion(ERROR_FUNC_DESC+op);
		}

		ArrayList<DiccionarioTiposInfo> listaCumplenCantParam = new
			ArrayList<DiccionarioTiposInfo>();

		//pone en listaCumplenCantParam solo aquellas funciones con la cantidad
		//de parámetros solicitados
		for(DiccionarioTiposInfo dti : listaFunciones)
		{
			
			if(dti.getCantParam() == cantParam)
			{
				listaCumplenCantParam.add(dti);
			}
		}
		return listaCumplenCantParam;
	}

/*
	//EJEMPLO DE USO DE UN ENUM INTERNO...
	enum Posi{
			 BOOL, ENTERO, FLOTANTE, CUALITATIVO,
			 RANGO, LISTA;
	}
	public static void main(String a[])
	{
		DiccionarioTipos dt = DiccionarioTipos.getInstance();
		dt.buscaFuncion("neto", 2);
		DataObjectTipos dataT = DataObjectTipos.ENTERO;
		Posi pos = Posi.ENTERO;

		if(dataT.equals(pos))
		{
			//no entra
		}
		if(pos.toString().equals(dataT.toString()))
		{
			//si entra
		}
	}
//*/
	//NATIVOS DEL ENUM:
	// enumType valueOf(String)
	// int ordinal()
	// for(Planet p : Planet.values())


}
